EKS Auto Mode で独自の NodePool を作成して利用してみた

EKS Auto Mode で独自の NodePool を作成して利用してみた

EKS Auto Mode にはノードを管理するための概念として、下記が存在します(正確にはマネージドな形で実行されている Karpenterの概念ですが)。

  • NodeClass
    • ネットワーク設定、ストレージ設定、リソースのタグ付けなど、AWS インフラレイヤーの詳細な設定を行う
  • NodePool
    • インスタンスタイプ、CPU アーキテクチャ (ARM64/AMD64)やキャパシティタイプ (スポット/オンデマンド)など、コンテナを起動するリソースの条件を指定する
    • NodePool の中で NodeClass を指定して作成する
  • NodeClaim
    • NodeClass/NodePool を元に、実際に EKS ノードを作成してノードのライフサイクルを管理する

NodeClass と NodePool はデフォルトで用意されており、それを利用することも可能です。
ただし、スポットインスタンスを使いたい、GPU 系インスタンスを使いたいといった理由でユーザー側で作成することも可能です。
今回は実際に NodePool を作成して利用してみました。

デフォルトの NodeClass/NodePool を確認してみる

NodeClass はデフォルトで 1 つ存在します。

% kubectl describe nodeclass
Name:         default
Namespace:
Labels:       app.kubernetes.io/managed-by=eks
Annotations:  eks.amazonaws.com/nodeclass-hash: 16389426616597791172
              eks.amazonaws.com/nodeclass-hash-version: v1
API Version:  eks.amazonaws.com/v1
Kind:         NodeClass
Metadata:
  Creation Timestamp:  2024-12-29T08:55:27Z
  Finalizers:
    eks.amazonaws.com/termination
  Generation:        1
  Resource Version:  1029735
  UID:               f5ab30b9-e11d-496a-983d-19e86dda727b
Spec:
  Ephemeral Storage:
    Iops:                     3000
    Size:                     80Gi
    Throughput:               125
  Network Policy:             DefaultAllow
  Network Policy Event Logs:  Disabled
  Role:                       test-cluster-eks-auto-20241229084518842100000001
  Security Group Selector Terms:
    Id:         sg-0995cc0a1e6144d0a
  Snat Policy:  Random
  Subnet Selector Terms:
    Id:  subnet-06e81109a96d281a4
    Id:  subnet-07858fbd2ef50fe8a
    Id:  subnet-093c625d37faa0de0
Status:
  Conditions:
    Last Transition Time:  2024-12-29T08:56:13Z
    Message:
    Observed Generation:   1
    Reason:                SubnetsReady
    Status:                True
    Type:                  SubnetsReady
    Last Transition Time:  2024-12-29T08:56:13Z
    Message:
    Observed Generation:   1
    Reason:                SecurityGroupsReady
    Status:                True
    Type:                  SecurityGroupsReady
    Last Transition Time:  2024-12-29T08:56:13Z
    Message:
    Observed Generation:   1
    Reason:                InstanceProfileReady
    Status:                True
    Type:                  InstanceProfileReady
    Last Transition Time:  2024-12-29T08:56:13Z
    Message:
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Instance Profile:        eks-ap-northeast-1-test-cluster-11975232971565303637
  Security Groups:
    Id:    sg-0995cc0a1e6144d0a
    Name:  eks-cluster-sg-test-cluster-1656125505
  Subnets:
    Id:       subnet-07858fbd2ef50fe8a
    Zone:     ap-northeast-1c
    Zone ID:  apne1-az1
    Id:       subnet-06e81109a96d281a4
    Zone:     ap-northeast-1d
    Zone ID:  apne1-az2
    Id:       subnet-093c625d37faa0de0
    Zone:     ap-northeast-1a
    Zone ID:  apne1-az4
Events:
  Type    Reason     Age                 From       Message
  ----    ------     ----                ----       -------
  Normal  Finalized  31m (x17 over 10h)  karpenter  Finalized eks.amazonaws.com/termination

NodePool と比較すれば設定変更する機会は少ないと思いますが、エファメラルストレージを拡張したり、セキュリティグループを変更したりといった目的で新しく作成することができます。
また、NodePool はデフォルトで general-purpose と system が存在します。
generarl-purpose は一般的なコンテナアプリケーション用に作成されており、C 系、M 系、R 系のオンデマンドインスタンスを利用できます。
また、CPU アーキテクチャとしては amd64 が指定されています。
したがって、C 系、M 系、R 系以外の GPU などのインスタンスや、スポットインスタンス、ARM 系インスタンスなどを利用する場合は独自の NodePool を作成する必要があります。

% kubectl describe nodepool general-purpose
Name:         general-purpose
Namespace:
Labels:       app.kubernetes.io/managed-by=eks
Annotations:  karpenter.sh/nodepool-hash: 4012513481623584108
              karpenter.sh/nodepool-hash-version: v3
API Version:  karpenter.sh/v1
Kind:         NodePool
Metadata:
  Creation Timestamp:  2024-12-29T08:55:27Z
  Generation:          1
  Resource Version:    28506
  UID:                 472bd24c-6606-4b3e-86dd-d45de8e35008
Spec:
  Disruption:
    Budgets:
      Nodes:               10%
    Consolidate After:     30s
    Consolidation Policy:  WhenEmptyOrUnderutilized
  Template:
    Metadata:
    Spec:
      Expire After:  336h
      Node Class Ref:
        Group:  eks.amazonaws.com
        Kind:   NodeClass
        Name:   default
      Requirements:
        Key:       karpenter.sh/capacity-type
        Operator:  In
        Values:
          on-demand
        Key:       eks.amazonaws.com/instance-category
        Operator:  In
        Values:
          c
          m
          r
        Key:       eks.amazonaws.com/instance-generation
        Operator:  Gt
        Values:
          4
        Key:       kubernetes.io/arch
        Operator:  In
        Values:
          amd64
        Key:       kubernetes.io/os
        Operator:  In
        Values:
          linux
      Termination Grace Period:  24h0m0s
Status:
  Conditions:
    Last Transition Time:  2024-12-29T08:56:11Z
    Message:
    Observed Generation:   1
    Reason:                ValidationSucceeded
    Status:                True
    Type:                  ValidationSucceeded
    Last Transition Time:  2024-12-29T08:56:13Z
    Message:
    Observed Generation:   1
    Reason:                NodeClassReady
    Status:                True
    Type:                  NodeClassReady
    Last Transition Time:  2024-12-29T08:56:13Z
    Message:
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Resources:
    Cpu:                  0
    Ephemeral - Storage:  0
    Memory:               0
    Nodes:                0
    Pods:                 0
Events:                   <none>

system と呼ばれる NodePool も存在していますが、こちらは CoreDNS などのクラスター運用に重要なリソースを配置するための NodePool になります。
Taint として CriticalAddonsOnly が設定されており、明示的に Toleration を設定した Pod 以外は配置できないようになっています。

% kubectl describe nodepool system
Name:         system
Namespace:
Labels:       app.kubernetes.io/managed-by=eks
Annotations:  karpenter.sh/nodepool-hash: 4982684901400657622
              karpenter.sh/nodepool-hash-version: v3
API Version:  karpenter.sh/v1
Kind:         NodePool
Metadata:
  Creation Timestamp:  2024-12-31T14:13:29Z
  Generation:          1
  Resource Version:    1010214
  UID:                 4ed55c2d-7494-40b1-9e82-508030353d22
Spec:
  Disruption:
    Budgets:
      Nodes:               10%
    Consolidate After:     30s
    Consolidation Policy:  WhenEmptyOrUnderutilized
  Template:
    Metadata:
    Spec:
      Expire After:  336h
      Node Class Ref:
        Group:  eks.amazonaws.com
        Kind:   NodeClass
        Name:   default
      Requirements:
        Key:       karpenter.sh/capacity-type
        Operator:  In
        Values:
          on-demand
        Key:       eks.amazonaws.com/instance-category
        Operator:  In
        Values:
          c
          m
          r
        Key:       eks.amazonaws.com/instance-generation
        Operator:  Gt
        Values:
          4
        Key:       kubernetes.io/arch
        Operator:  In
        Values:
          amd64
          arm64
        Key:       kubernetes.io/os
        Operator:  In
        Values:
          linux
      Taints:
        Effect:                  NoSchedule
        Key:                     CriticalAddonsOnly
      Termination Grace Period:  24h0m0s
Status:
  Conditions:
    Last Transition Time:  2024-12-31T14:13:29Z
    Message:
    Observed Generation:   1
    Reason:                NodeClassReady
    Status:                True
    Type:                  NodeClassReady
    Last Transition Time:  2024-12-31T14:13:29Z
    Message:
    Observed Generation:   1
    Reason:                ValidationSucceeded
    Status:                True
    Type:                  ValidationSucceeded
    Last Transition Time:  2024-12-31T14:13:29Z
    Message:
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Resources:
    Cpu:                  0
    Ephemeral - Storage:  0
    Memory:               0
    Nodes:                0
    Pods:                 0
Events:                   <none>

下記のような、toleration 設定を行うことで初めて system の NodePool が利用されます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      nodeSelector:
        karpenter.sh/nodepool: system
      tolerations:
        - key: "CriticalAddonsOnly"
          operator: "Exists"
      containers:
        - name: app
          image: nginx:latest
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"

Run critical add-ons on dedicated instances

重要なリソースをアプリケーション用のリソースとは違うノードに配置したい場合に利用すると良いでしょう。

NodePool を作って利用してみる

今回は Spot インスタンスを利用したいケースを想定して、独自の NodePool を作成してみます。
Auto Mode を有効化した、EKSv1.31 で試していきます。
NodeClass は特に変更せず、default で用意されているものを指定します。

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      nodeClassRef:
        group: eks.amazonaws.com
        kind: NodeClass
        name: default
      requirements:
        - key: "karpenter.sh/capacity-type"
          operator: In
          values: ["on-demand", "spot"]
        - key: "eks.amazonaws.com/instance-category"
          operator: In
          values: ["c", "m", "r"]
        - key: "eks.amazonaws.com/instance-cpu"
          operator: In
          values: ["2", "4", "8"]
        - key: "kubernetes.io/arch"
          operator: In
          values: ["amd64"]

spec.requirements として、利用する EC2 を指定します。
今回は karpenter.sh/capacity-type として spot を追加して、スポットインスタンスを利用できるようにします。
純粋な Karpenter の時とは微妙にキーが変わっているので、注意が必要です。
(例) karpenter.k8s.aws/instance-familyeks.amazonaws.com/instance-family
キーを指定する際は公式ドキュメントを確認することを推奨します。

https://docs.aws.amazon.com/eks/latest/userguide/create-node-pool.html

ここを間違えると NodePool の Status に invalid value としてメッセージが出るので、上手く動作しない場合は確認してみて下さい。


Status:
Conditions:
Last Transition Time: 2024-12-30T03:14:03Z
Message:
Observed Generation: 1
Reason: NodeClassReady
Status: True
Type: NodeClassReady
Last Transition Time: 2024-12-30T03:14:03Z
Message: invalid value: label karpenter.k8s.aws/instance-family is restricted; specify a well known label: [app.kubernetes.io/managed-by eks.amazonaws.com/compute-type eks.amazonaws.com/instance-accelerator-count eks.amazonaws.com/instance-accelerator-manufacturer eks.amazonaws.com/instance-accelerator-name eks.amazonaws.com/instance-category eks.amazonaws.com/instance-cpu eks.amazonaws.com/instance-cpu-manufacturer eks.amazonaws.com/instance-cpu-sustained-clock-speed-mhz eks.amazonaws.com/instance-ebs-bandwidth eks.amazonaws.com/instance-encryption-in-transit-supported eks.amazonaws.com/instance-family eks.amazonaws.com/instance-generation eks.amazonaws.com/instance-gpu-count eks.amazonaws.com/instance-gpu-manufacturer eks.amazonaws.com/instance-gpu-memory eks.amazonaws.com/instance-gpu-name eks.amazonaws.com/instance-hypervisor eks.amazonaws.com/instance-local-nvme eks.amazonaws.com/instance-memory eks.amazonaws.com/instance-network-bandwidth eks.amazonaws.com/instance-size karpenter.sh/capacity-type karpenter.sh/nodepool kubernetes.io/arch kubernetes.io/os node.kubernetes.io/instance-type node.kubernetes.io/windows-build topology.k8s.aws/zone-id topology.kubernetes.io/region topology.kubernetes.io/zone], or a custom label that does not use a restricted domain: [k8s.io karpenter.k8s.aws karpenter.sh kubernetes.io] in requirements, restricted

上記マニフェストファイルを適用すると、Status としても Ready になりました。

% kubectl describe nodepool default
Name:         default
Namespace:
Labels:       <none>
Annotations:  karpenter.sh/nodepool-hash: 4012513481623584108
              karpenter.sh/nodepool-hash-version: v3
API Version:  karpenter.sh/v1
Kind:         NodePool
Metadata:
  Creation Timestamp:  2024-12-31T14:16:00Z
  Generation:          4
  Resource Version:    1046049
  UID:                 d3c2a42d-ba3e-4d50-8856-d2d8bb4e9562
Spec:
  Disruption:
    Budgets:
      Nodes:               10%
    Consolidate After:     0s
    Consolidation Policy:  WhenEmptyOrUnderutilized
  Template:
    Spec:
      Expire After:  336h
      Node Class Ref:
        Group:  eks.amazonaws.com
        Kind:   NodeClass
        Name:   default
      Requirements:
        Key:       karpenter.sh/capacity-type
        Operator:  In
        Values:
          on-demand
          spot
        Key:       eks.amazonaws.com/instance-category
        Operator:  In
        Values:
          c
          m
          r
        Key:       eks.amazonaws.com/instance-cpu
        Operator:  In
        Values:
          2
          4
          8
        Key:       kubernetes.io/arch
        Operator:  In
        Values:
          amd64
      Termination Grace Period:  24h
Status:
  Conditions:
    Last Transition Time:  2024-12-31T14:16:00Z
    Message:
    Observed Generation:   4
    Reason:                ValidationSucceeded
    Status:                True
    Type:                  ValidationSucceeded
    Last Transition Time:  2024-12-31T14:16:00Z
    Message:
    Observed Generation:   4
    Reason:                NodeClassReady
    Status:                True
    Type:                  NodeClassReady
    Last Transition Time:  2024-12-31T16:05:10Z
    Message:
    Observed Generation:   4
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Resources:
    Cpu:                  0
    Ephemeral - Storage:  0
    Memory:               0
    Nodes:                0
    Pods:                 0
Events:
  Type    Reason  Age                 From       Message
  ----    ------  ----                ----       -------
  Normal  Ready   65s (x4 over 110m)  karpenter  Status condition transitioned, Type: Ready, Status: Unknown -> True, Reason: Ready
  Normal  Ready   65s (x2 over 105m)  karpenter  Status condition transitioned, Type: Ready, Status: True -> Unknown, Reason: ReconcilingDependents, Message: NodeClassReady=True

NodeSelector でスポットインスタンスの利用を指定して、Nginx コンテナを起動してみます。

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: nginx
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      nodeSelector:
        karpenter.sh/nodepool: default
        karpenter.sh/capacity-type: spot
      containers:
        - image: nginx:1.14.2
          imagePullPolicy: Always
          name: nginx
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "0.5"

NodePool として作成した default を指定しつつ、スポットインスタンスをリクエストしています。
明示的に NodePool を指定しなくても条件に応じて最適な NodePool が選択されるため、NodePool の指定は必須ではありません。
複数の NodePool が条件を満たす際に動作が分かり辛くなりそうだったので、今回は明示的に指定しています。
利用しない組み込みの NodePool は EKS の設定で無効化できるので、必要に応じて設定変更すると良いかと思います。

https://docs.aws.amazon.com/eks/latest/userguide/set-builtin-node-pools.html

しばらくして、無事 Pod が作成されてました(下記コマンドの実行タイミングが遅れただけで 1 分弱でノードが作成されます)。

% kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
nginx-85c5b79bd8-j4k2r   1/1     Running   0          3m19s
nginx-85c5b79bd8-t2c9j   1/1     Running   0          3m19s

実際に EKS ノードを作成する NodeClaim も作成されています。
NodePool の段階ではオンデマンド or スポットなど、ある程度幅のある指定でしたが、NodeClaim が作成されるタイミングでは各設定値が決定されています。
karpenter.sh/nodepool=default と書いてあるので、作成した NodePool がちゃんと利用されています。

% kubectl describe nodeclaim
Name:         default-xngjq
Namespace:
Labels:       app.kubernetes.io/managed-by=eks
              eks.amazonaws.com/compute-type=auto
              eks.amazonaws.com/instance-category=c
              eks.amazonaws.com/instance-cpu=2
              eks.amazonaws.com/instance-cpu-manufacturer=amd
              eks.amazonaws.com/instance-cpu-sustained-clock-speed-mhz=3300
              eks.amazonaws.com/instance-ebs-bandwidth=3170
              eks.amazonaws.com/instance-encryption-in-transit-supported=true
              eks.amazonaws.com/instance-family=c5a
              eks.amazonaws.com/instance-generation=5
              eks.amazonaws.com/instance-hypervisor=nitro
              eks.amazonaws.com/instance-memory=4096
              eks.amazonaws.com/instance-network-bandwidth=750
              eks.amazonaws.com/instance-size=large
              eks.amazonaws.com/nodeclass=default
              karpenter.sh/capacity-type=spot
              karpenter.sh/nodepool=default
              kubernetes.io/arch=amd64
              kubernetes.io/os=linux
              node.kubernetes.io/instance-type=c5a.large
              topology.k8s.aws/zone-id=apne1-az1
              topology.kubernetes.io/region=ap-northeast-1
              topology.kubernetes.io/zone=ap-northeast-1c
Annotations:  eks.amazonaws.com/nodeclass-hash: 16389426616597791172
              eks.amazonaws.com/nodeclass-hash-version: v1
              karpenter.sh/nodepool-hash: 4012513481623584108
              karpenter.sh/nodepool-hash-version: v3
API Version:  karpenter.sh/v1
Kind:         NodeClaim
Metadata:
  Creation Timestamp:  2024-12-31T16:13:48Z
  Finalizers:
    karpenter.sh/termination
  Generate Name:  default-
  Generation:     1
  Owner References:
    API Version:           karpenter.sh/v1
    Block Owner Deletion:  true
    Kind:                  NodePool
    Name:                  default
    UID:                   d3c2a42d-ba3e-4d50-8856-d2d8bb4e9562
  Resource Version:        1048982
  UID:                     3a993ddb-27d2-4a34-bfee-88778914e710
Spec:
  Expire After:  336h
  Node Class Ref:
    Group:  eks.amazonaws.com
    Kind:   NodeClass
    Name:   default
  Requirements:
    Key:       node.kubernetes.io/instance-type
    Operator:  In
    Values:
      c4.large
      c4.xlarge
      c5.large
      c5.xlarge
      c5a.large
      c5a.xlarge
      c5d.large
      c5n.large
      c6a.large
      c6a.xlarge
      c6i.large
      c6i.xlarge
      c6id.large
      c6id.xlarge
      c6in.large
      c6in.xlarge
      c7a.large
      c7a.xlarge
      c7i-flex.large
      c7i-flex.xlarge
      c7i.large
      c7i.xlarge
      m4.large
      m4.xlarge
      m5.large
      m5.xlarge
      m5a.large
      m5ad.large
      m5d.large
      m5dn.large
      m5n.large
      m5zn.large
      m6a.large
      m6a.xlarge
      m6i.large
      m6i.xlarge
      m6id.large
      m6idn.large
      m6in.large
      m7a.large
      m7i-flex.large
      m7i-flex.xlarge
      m7i.large
      r4.large
      r5.large
      r5a.large
      r5ad.large
      r5b.large
      r5d.large
      r5dn.large
      r5n.large
      r6a.large
      r6a.xlarge
      r6i.large
      r6id.large
      r6idn.large
      r6in.large
      r7a.large
      r7i.large
      r7iz.large
    Key:       eks.amazonaws.com/instance-cpu
    Operator:  In
    Values:
      2
      4
      8
    Key:       karpenter.sh/nodepool
    Operator:  In
    Values:
      default
    Key:       eks.amazonaws.com/nodeclass
    Operator:  In
    Values:
      default
    Key:       kubernetes.io/arch
    Operator:  In
    Values:
      amd64
    Key:       karpenter.sh/capacity-type
    Operator:  In
    Values:
      spot
    Key:       eks.amazonaws.com/instance-category
    Operator:  In
    Values:
      c
      m
      r
  Resources:
    Requests:
      Cpu:                   1
      Pods:                  2
  Termination Grace Period:  24h0m0s
Status:
  Allocatable:
    Cpu:                  1480m
    Ephemeral - Storage:  71Gi
    Memory:               2699Mi
    Pods:                 27
  Capacity:
    Cpu:                  2
    Ephemeral - Storage:  80Gi
    Memory:               3788Mi
    Pods:                 27
  Conditions:
    Last Transition Time:  2024-12-31T16:13:51Z
    Message:
    Observed Generation:   1
    Reason:                Launched
    Status:                True
    Type:                  Launched
    Last Transition Time:  2024-12-31T16:14:05Z
    Message:
    Observed Generation:   1
    Reason:                Registered
    Status:                True
    Type:                  Registered
    Last Transition Time:  2024-12-31T16:14:07Z
    Message:
    Observed Generation:   1
    Reason:                Initialized
    Status:                True
    Type:                  Initialized
    Last Transition Time:  2024-12-31T16:14:07Z
    Message:
    Observed Generation:   1
    Reason:                Consolidatable
    Status:                True
    Type:                  Consolidatable
    Last Transition Time:  2024-12-31T16:14:07Z
    Message:
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Image ID:                ami-0a6f527d62b57d7ad
  Last Pod Event Time:     2024-12-31T16:14:12Z
  Node Name:               i-07a9c99a235f1d699
  Provider ID:             aws:///ap-northeast-1c/i-07a9c99a235f1d699
Events:
  Type    Reason             Age    From       Message
  ----    ------             ----   ----       -------
  Normal  Launched           4m25s  karpenter  Status condition transitioned, Type: Launched, Status: Unknown -> True, Reason: Launched
  Normal  DisruptionBlocked  4m20s  karpenter  Cannot disrupt NodeClaim: nodeclaim does not have an associated node
  Normal  Registered         4m10s  karpenter  Status condition transitioned, Type: Registered, Status: Unknown -> True, Reason: Registered
  Normal  Initialized        4m9s   karpenter  Status condition transitioned, Type: Initialized, Status: Unknown -> True, Reason: Initialized
  Normal  Ready              4m9s   karpenter  Status condition transitioned, Type: Ready, Status: Unknown -> True, Reason: Ready
  Normal  Unconsolidatable   3m39s  karpenter  Can't replace with a cheaper node

c5a.large のスポットインスタンスがリクエストされてました。

スクリーンショット 2025-01-01 1.20.16.png

まとめ

今回は独自の NodePool を作成して利用してみました。
同様の方法で GPU 系インスタンスや、Arm 系のインスタンスも利用できるはずです。
一つのクラスターで簡単にスポットインスタンス、GPU 系インスタンス、Arm 系インスタンスなどを使い分けることができるのは EKS Auto Mode の大きな強みだと思います。
特に GPU 利用については、EKS Auto Mode を使う強力なモチベーションになると思うので別の機会に試してみます。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.